package com.zhike.service.im.manager;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.eva.epc.common.util.CommonUtils;
import com.google.gson.Gson;
import com.zhike.common.constant.SysConfigConstants;
import com.zhike.common.dto.FriendUIdDTO;
import com.zhike.common.dto.MsgBody4Guest;
import com.zhike.common.dto.UserProtocalsType;
import com.zhike.common.enums.ResultCode;
import com.zhike.common.exception.ZkimException;
import com.zhike.common.model.Device;
import com.zhike.common.vo.FriendRequestVO;
import com.zhike.common.vo.RosterVO;
import com.zhike.common.vo.UserInfo4Cache;
import com.zhike.mapper.friends.FriendsMapper;
import com.zhike.mapper.system.DeviceMapper;
import com.zhike.mapper.user.UsersMapper;
import com.zhike.mobileimsdk.server.protocal.Protocal;
import com.zhike.service.im.ChatServerLauncher;
import com.zhike.service.messages.IChatSessionService;
import com.zhike.service.system.ISystemConfigService;
import com.zhike.service.system.ISystemService;
import com.zhike.utils.MessageHelper;
import com.zhike.utils.MessageHelper.SendResultObserver;
import com.zhike.utils.cache.UsersInfoCacheProvider;
import com.zhike.utils.manager.UsersStatusManager;

import lombok.extern.slf4j.Slf4j;

/**
 * 处理好友相关详细的管理类
 */
@Component
@Slf4j
public class FriendMessageManager {

    @Autowired
    private MessageHelper messageHelper;

    @Resource
    private FriendsMapper friendsMapper;

    @Autowired
    private ISystemService systemService;

    @Autowired
    private UsersInfoCacheProvider usersInfoCacheProvider;

    @Autowired
    private ISystemConfigService systemConfigService;

    @Autowired
    private UsersStatusManager usersStatusManager;

    @Autowired
    private DeviceMapper deviceMapper;

    @Resource
    private UsersMapper usersMapper;

    @Autowired
    private IChatSessionService chatSessionService;

    @Autowired
    private MessageRecordManager messageRecordManager;

    /**
     * 处理好友邀请
     * 
     * @param dto
     * @return
     */
    public Boolean processMT5_ADD_FRIEND_REQUEST(String uid, FriendUIdDTO dto) {
        // 从缓存获取当前用户信息
        UserInfo4Cache rdsUser = systemService.getUserInfo();
        if (null == rdsUser) {
            throw new ZkimException(ResultCode.SYS_SA_NO_SESSION);
        }

        UserInfo4Cache targetUser = usersInfoCacheProvider.get(dto.getFriendUid());
        if (null == targetUser) {
            throw new ZkimException(ResultCode.USER_NOT_EXSIT);
        }

        try {
            for (RosterVO vo : rdsUser.getRosterList()) {
                if (vo.getFriendUserUid().equals(dto.getFriendUid())) {
                    String responseForError = "已经是好友了，无需重复添加";
                    // 把错误信息反馈给加好友请求者A
                    messageHelper.sendAddFriendRequestResponse_for$error_server$to$aMessage(
                            rdsUser.getCurrentDeviceId(), responseForError, null);
                    throw new ZkimException(ResultCode.FRIEND_RELATED);
                }
            }

            // ** 查询该人员的允许最大好友数、当前好友总数并判断是否超出最大好友数
            UserInfo4Cache uc = usersInfoCacheProvider.get(rdsUser.getFakeUid());
            int friendCount = uc.getRosterList().size();
            int maxFriend = CommonUtils
                    .getIntValue(systemConfigService.getValueByKey(SysConfigConstants.CONFIG_TYPE_IM,
                            SysConfigConstants.CONFIG_MAX_FRIEND_COUNT,
                            SysConfigConstants.DEFAULT_VAL));
            log.debug("[ZKIMChat]【发出】" + rdsUser.getFakeUid()
                    + "：最大好友数=" + maxFriend + "、当前总数=" + friendCount);

            // 当前好友总数>=最大允许可友总数时则不允许再加好友了
            if (friendCount >= maxFriend) {
                throw new ZkimException(ResultCode.FRIEND_MAX);
            }

            Integer reqCount = friendsMapper.queryFriendsRequestCountByUidFriendUid(
                    rdsUser.getFakeUid(), dto.getFriendUid());
            if (reqCount > 0) {
                log.debug("[ZKIMChat]由uid=" + rdsUser.getFakeUid()
                        + "发起的加好友uid=" + dto.getFriendUid()
                        + "的请求，已经存在，更新最近请求时间即可.");

                friendsMapper.updateFriendsRequestTime(dto.getDesc(),
                        System.currentTimeMillis(),
                        rdsUser.getFakeUid(),
                        dto.getFriendUid());
            } else {
                // 否则是首次加该发友，插入一条邀请到数据库（此种情况是防止对方不在线，下次上线了还能来处理的情况）
                if (friendsMapper.insertFriendsRequestData(rdsUser.getFakeUid(), dto.getFriendUid(), dto.getDesc(),
                        System.currentTimeMillis()) == 0) {
                    log.debug("[ZKIMChat]由uid=" + rdsUser.getFakeUid()
                            + "发起的加好友uid=" + dto.getFriendUid()
                            + "的请求数据插入失败.");
                    throw new ZkimException(ResultCode.DB_ERROR);
                }

            }

            MessageHelper.SendResultObserver sendResultObserver = new MessageHelper.SendResultObserver() {

                @Override
                public void update(boolean code, Protocal p) {
                    if (code) {
                        log.debug("[ZKIMChat]服务器通知由uid=" + uid
                                + "发起的加好友请求，已经成功发给在线好友uid=" + dto.getFriendUid() + ".");
                    }
                }
            };

            // 发送加好友消息通知
            List<Device> devices = deviceMapper.queryDevices(dto.getFriendUid());
            for (Device d : devices) {
                messageHelper.sendAddFriendRequestInfo_server$to$bMessage(d.getDeviceNo(), uc,
                        sendResultObserver);
            }

        } catch (Exception e) {
            log.error("{}", e.getMessage());
            throw new ZkimException(ResultCode.OTHER_ERROR);
        }

        return Boolean.TRUE;
    }

    /**
     * 处理好友请求(同意)指令的投递
     * 
     * @param dto
     * @return
     * @throws Exception
     */
    public Boolean processAddFriendReqAgree(final FriendUIdDTO dto) throws Exception {
        // 从缓存获取当前用户信息
        UserInfo4Cache rdsUser = systemService.getUserInfo();
        if (rdsUser == null) {
            return false;
        }
        UserInfo4Cache uc = usersInfoCacheProvider.get(rdsUser.getFakeUid());
        int currentfriendCount = uc.getRosterList().size();

        int maxFriend = CommonUtils
                .getIntValue(systemConfigService.getValueByKey(SysConfigConstants.CONFIG_TYPE_IM,
                        SysConfigConstants.CONFIG_MAX_FRIEND_COUNT));
        // 校验是否超出最大的好友数
        if (currentfriendCount >= maxFriend) {
            throw new ZkimException(ResultCode.FRIEND_MAX);
        }

        String desc = dto.getDesc();
        if (StringUtils.isBlank(desc)) {
            FriendRequestVO requestData = friendsMapper.selectFriendRequestOne(dto.getFriendUid(), uc.getFakeUid());
            if (null != requestData) {
                desc = requestData.getBeDesc();
            }
        }

        // 查询好友
        Integer friendsCount = friendsMapper.queryFriendsCountByUidFriendUid(
                rdsUser.getFakeUid(), dto.getFriendUid());
        if (friendsCount <= 0) {
            // 插入B的好友记录（好友是A）
            if (friendsMapper.insertFriendsData(rdsUser.getFakeUid(), dto.getFriendUid()) > 0) {
                // 更新状态
                Integer res = friendsMapper.updateFriendsRequestStatus(dto.getFriendUid(), rdsUser.getFakeUid(), 1);
                if (res > 0) {
                    usersInfoCacheProvider.reloadOneCache(rdsUser.getFakeUid());
                }
            }
        }

        Integer friendsCountB = friendsMapper.queryFriendsCountByUidFriendUid(dto.getFriendUid(),
                rdsUser.getFakeUid());
        if (friendsCountB <= 0) {
            // 插入A的好友记录（好友是B）
            if (friendsMapper.insertFriendsData(dto.getFriendUid(), rdsUser.getFakeUid()) > 0) {
                usersInfoCacheProvider.reloadOneCache(dto.getFriendUid());
            }
        }

        // 初始化会话
        String sessionId = chatSessionService.createSession(dto.getFriendUid(), uc.getFakeUid());

        String fp = Protocal.genFingerPrint();
        UserInfo4Cache user1 = usersInfoCacheProvider.get(dto.getFriendUid());
        UserInfo4Cache user2 = usersInfoCacheProvider.get(uc.getFakeUid());
        // List<String> devices = deviceMapper.queryDevicesByUser(dto.getFriendUid());
        boolean flag = false;

        for (Device device : user1.getDeviceList()) {
            if (usersStatusManager.isOnline(device.getDeviceNo())) {
                log.debug("-UID: {} -发送目标: {}", dto.getFriendUid(), device.getDeviceNo());
                messageHelper.sendAddFriendAgree_server$to$Message(device.getDeviceNo(), "agree",
                        null);
                MsgBody4Guest body = new MsgBody4Guest();
                body.setF(uc.getFakeUid());
                body.setT(dto.getFriendUid());
                body.setNickName(user2.getNickName());
                body.setCy(0);
                body.setTy(0);
                body.setM(desc);
                String content = new Gson().toJson(body);
                messageHelper.sendMessage(device.getDeviceNo(), content, false,
                        UserProtocalsType.MT03_OF_CHATTING_MESSAGE,
                        null);
            }

            if (!flag) {
                messageRecordManager.saveMessageAndUpdateSession(sessionId, dto.getFriendUid(), uc.getFakeUid(), 0, 0,
                        desc, fp);
            }
            flag = true;
        }

        for (Device device : user2.getDeviceList()) {
            if (usersStatusManager.isOnline(device.getDeviceNo())) {
                messageHelper.sendAddFriendAgree_server$to$Message(device.getDeviceNo(), "agree",
                        null);

                MsgBody4Guest body = new MsgBody4Guest();
                body.setF(dto.getFriendUid());
                body.setT(uc.getFakeUid());
                body.setNickName(user1.getNickName());
                body.setCy(0);
                body.setTy(0);

                body.setM(desc);
                String content = new Gson().toJson(body);
                log.debug("-UID2: {} -发送目标: {}", uc.getFakeUid(), device.getDeviceNo());
                messageHelper.sendMessage(device.getDeviceNo(), content, false,
                        UserProtocalsType.MT03_OF_CHATTING_MESSAGE,
                        null);

            }
        }

        return true;
    }

    /**
     * 处理好友请求（拒绝）
     * 
     * @param dto
     * @return
     * @throws Exception
     */
    public boolean processAddFriendRefused(final FriendUIdDTO dto) throws Exception {
        UserInfo4Cache rdsUser = systemService.getUserInfo();
        if (rdsUser == null) {
            return false;
        }
        // UserInfo4Cache uc = usersInfoCacheProvider.get(rdsUser.getUserId());
        boolean success = false;
        String responseForError = null;
        try {

            // 存在就删除
            Integer friendsReqCount = friendsMapper.queryFriendsRequestCountByUidFriendUid(dto.getFriendUid(),
                    rdsUser.getFakeUid());

            if (friendsReqCount > 0) {
                // 更新状态为拒绝
                Integer res = friendsMapper.updateFriendsRequestStatus(dto.getFriendUid(), rdsUser.getFakeUid(), 2);
                if (res > 0) {
                    success = true;
                }
            }

        } catch (Exception e) {
            responseForError = "Prompt: Reject id:"
                    + rdsUser.getFakeUid() + " for add lover failed, reason is " + e.getMessage();
        }

        // **××××××××××××××××××××××××××××××××××××××××××××××××××××××××××× 处理成功， 通知被拒
        // 拒绝者的客户端（如果在线的话，不在线当然就不需要通知了，下次登陆即可载入数据库中存放的提示信息即可）
        String localUser_id = "-1";
        if (success) {
            // ** 尝试取出B用户和原A用户的在线user_id
            // =======================================================
            String srcUser_id = rdsUser.getFakeUid();
            localUser_id = dto.getFriendUid();

            // A用户是否在线
            final boolean srcUserOnline = usersStatusManager.isOnline(srcUser_id);

            // 指令发送结果观察者（因为通信底层netty是异步通知数据发送结果的）
            SendResultObserver sendResultObserver = new SendResultObserver() {
                @Override
                public void update(boolean code, Protocal p) {
                    // 返回值true：对于应用层逻辑来说，无论是在线发送成功，或者是跨服桥接成功，都算是发送成功
                    if (code) {
                        // 说明：满足此条件，表示本地不在线但处于与Web互通模式的情况下，消息却发送成功，也需要进行App的push通知，
                        // 因为前提是app端已不在线，进行push是合情合理的（否则桥接成功的情况下，app这边即使不在线也永远收
                        // 不到app的push通知，除非web那端也要实现不在线时的app的push通知。
                        // 备忘：此处的push逻辑是为了省去web那端不在线时的app push通知逻辑，日后支持全功能集群后，就不需
                        // 要这个逻辑了，因为包括web集群实例在内，都将是对等的且必须支持app 的push逻辑——所以没必要以下代码。
                        if (!srcUserOnline && ChatServerLauncher.bridgeEnabled) {
                            // ## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                            // apnsPushHelper.tryPushAddFriendProcess_reject(dto.getLocalUserNickName(),
                            // dto.getSrcUserUid());
                        }
                    } else {
                        // ** 备忘：目前拒绝好友请求没有做离线处理，因为从产品的角度来说，“拒绝”是种不好的体验，
                        // ** 目前可以采取静默处理（也就是不离线处理），一是技术上也简单，二是用户体验上
                        // ** 也更人性化，必竟对于被拒者来说，没有看到通过，那就有可能被拒或对方没看到，
                        // ** 从使用心理上来说，如果真的想要加对方，大不了再加一次或再加几次呗！

                        // ## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
                        // apnsPushHelper.tryPushAddFriendProcess_reject(dto.getLocalUserNickName(),
                        // dto.getSrcUserUid());
                    }
                }
            };

            // A用户在线（原加好友的发起人）
            // --> 将拒绝者的个人信息发给A（以便及时告之他，加好友被拒了！）：
            messageHelper.sendProcessAddFriendReqReject(srcUser_id,
                    usersMapper.getUserByUserId(rdsUser.getFakeUid()), sendResultObserver);
        }
        // ** 尝试将出错信息发回给处理加好友请求的B
        else {
            // B用户在线（本次处理好友请求的发起方）
            if (!"-1".equals(localUser_id)) {
                // 将出错信息及时反馈给B（本次处理的发起者）
                messageHelper.sendProcessAddFriendReq_response$For$Rrror_server$to$bMessage(localUser_id,
                        responseForError,
                        null);
            }
        }
        return success;
    }

}
